package cn.wujiangbo.service.impl;

import cn.wujiangbo.domain.Goods;
import cn.wujiangbo.lock.LockInterface;
import cn.wujiangbo.lock.ZKLock;
import cn.wujiangbo.mapper.GoodsMapper;
import cn.wujiangbo.service.OrderService;
import cn.wujiangbo.utils.RedissionUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private JedisPool jedisPool;

    @Autowired
    private GoodsMapper goodsMapper;

    /**
     * 模拟多线程下单场景
     * 我们采用Redis的LUA脚本解决超卖问题
     */
    @Override
    public ResponseEntity<String> insertOrderForRedis(Long userId) {
        String uuid = UUID.randomUUID().toString();
        Jedis jedis = jedisPool.getResource();
        //需要购买商品的ID
        Long goodsId = 1L;
        //需要购买的数量
        Integer num = 2;

        String result = jedis.set("123", uuid, "NX", "EX", 2);
        //获取锁并设置超时
        if("OK".equals(result)) {
            try{
                //操作数据库
                System.out.println("[用户：" + userId + "，线程：" + Thread.currentThread().getName() + "] 拿到锁了，开始下单");
                //查询库存
                Goods goods = goodsMapper.laodById(goodsId);
                if (goods.getCount() >= num){
                    //库存足够，那么就开始扣减库存
                    Map<String,Object> params = new HashMap<>();
                    params.put("id", goodsId);
                    params.put("num", num);
                    goodsMapper.updateNum(params);//update t_goods set count=count-2 where id = 1
                    System.out.println("[用户：" + userId + "，线程：" + Thread.currentThread().getName() + "] ************* 下单成功，库存扣减：" + num);
                    return new ResponseEntity<>("用户[" + userId + "]下单成功", HttpStatus.OK);
                }
                else{
                    System.out.println("[用户：" + userId + "，线程：" + Thread.currentThread().getName() + "] 拿到锁了，但库存不足，无法下单，很遗憾");
                    return new ResponseEntity<>("库存不足，用户[" + userId + "]下单失败", HttpStatus.NOT_FOUND);
                }
            }catch (Exception e){
                e.printStackTrace();
                return new ResponseEntity<>("下单失败，发生异常：" + e.getLocalizedMessage(), HttpStatus.NOT_FOUND);
            }finally {
                //lua脚本
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                //执行脚本
                jedis.eval(script, Collections.singletonList("123"), Collections.singletonList(uuid));
                jedis.close();
            }
        }
        else{
            System.out.println("[用户：" + userId + "，线程：" + Thread.currentThread().getName() + "] 没有拿到锁，开始自旋去获取锁");
            insertOrderForRedis(userId);
        }
        return null;
    }

    /**
     * 模拟多线程下单场景
     * 我们采用ZK的分布式锁解决超卖问题
     */
    @Override
    public ResponseEntity<String> insertOrderForZK(Long userId) {
        //需要购买商品的ID
        Long goodsId =1L;
        //需要购买的数量
        Integer num = 2;

        String resourceName = "goods_" + goodsId;//goods_1
        //获取ZK锁对象
        LockInterface lock = new ZKLock();
        try{
            //加锁
            lock.lock(resourceName);

            //拿到锁的线程开始做业务
            System.out.println("[用户：" + userId + "，线程：" + Thread.currentThread().getName() + "] 拿到锁了，开始下单");
            //通过商品id获取商品  3
            Goods goods = goodsMapper.laodById(goodsId);
            if (goods.getCount() >= num){
                Map<String,Object> params = new HashMap<>();
                params.put("id", goodsId);
                params.put("num", num);
                goodsMapper.updateNum(params);
                System.out.println("[用户：" + userId + "，线程：" + Thread.currentThread().getName() + "] ************* 下单成功，库存扣减：" + num);
                return new ResponseEntity<>("用户[" + userId + "]下单成功", HttpStatus.OK);
            }else{
                System.out.println("[用户：" + userId + "，线程：" + Thread.currentThread().getName() + "] 拿到锁了，但库存不足，无法下单，很遗憾");
                return new ResponseEntity<>("库存不足，用户[" + userId + "]下单失败", HttpStatus.NOT_FOUND);
            }
        }catch (Exception e){
            e.printStackTrace();
            return new ResponseEntity<>("下单失败，发生异常：" + e.getLocalizedMessage(), HttpStatus.NOT_FOUND);
        }
        finally {
            if (lock != null) {
                //释放锁
                lock.unlock(resourceName);
            }
        }
    }

    /**
     * 模拟多线程下单场景
     * 我们采用Redission的分布式锁解决超卖问题
     */
    @Override
    public ResponseEntity<String> insertOrderForRedission(Long userId) {
        //需要购买商品的ID
        Long goodsId =1L;
        //需要购买的数量
        Integer num = 2;

        //得到Redisson客户端，目的：操作Redis数据库
        RedissonClient redisson = RedissionUtils.getRedisson("127.0.0.1", "6379");
        //得到锁
        RLock rLock = RedissionUtils.getInstance().getRLock(redisson, "goods" + goodsId);
        try{
            //加锁
            rLock.lock();
            //打印一下，看哪个线程得到了锁
            System.out.println("[用户：" + userId + "，线程：" + Thread.currentThread().getName() + "] 拿到锁了，开始下单");
            //根据商品ID查询商品详情
            Goods goods = goodsMapper.laodById(goodsId);
            if (goods.getCount() >= num){
                Map<String,Object> params = new HashMap<>();
                params.put("id", goodsId);
                params.put("num", num);
                goodsMapper.updateNum(params);
                System.out.println("[用户：" + userId + "，线程：" + Thread.currentThread().getName() + "] ************* 下单成功，库存扣减：" + num);
                return new ResponseEntity<>("用户[" + userId + "]下单成功", HttpStatus.OK);
            }
            else{
                System.out.println("[用户：" + userId + "，线程：" + Thread.currentThread().getName() + "] 拿到锁了，但库存不足，无法下单，很遗憾");
                return new ResponseEntity<>("库存不足，用户[" + userId + "]下单失败", HttpStatus.NOT_FOUND);
            }
        }catch (Exception e){
            e.printStackTrace();
            return new ResponseEntity<>("下单失败，发生异常：" + e.getLocalizedMessage(), HttpStatus.NOT_FOUND);
        }
        finally {
            if (rLock != null) {
                rLock.unlock();
            }
        }
    }

    @Autowired
    private CuratorFramework curatorFramework;

    /**
     * 模拟多线程下单场景
     * 我们采用Curator的分布式锁解决超卖问题
     */
    @Override
    public ResponseEntity<String> insertOrderForCurator(Long userId) {
        //需要购买商品的ID
        Long goodsId =1L;
        //需要购买的数量
        Integer num = 2;

        //分布式锁
        InterProcessMutex lock = new InterProcessMutex(curatorFramework, "/order");
        try {
            //获取锁
            if (lock.acquire(1, TimeUnit.SECONDS) ){
                //拿到锁后，开始处理业务
                //打印一下，看哪个线程得到了锁
                System.out.println("[用户：" + userId + "，线程：" + Thread.currentThread().getName() + "] 拿到锁了，开始下单");
                //根据商品ID查询商品详情
                Goods goods = goodsMapper.laodById(goodsId);
                if (goods.getCount() >= num){
                    Map<String,Object> params = new HashMap<>();
                    params.put("id", goodsId);
                    params.put("num", num);
                    goodsMapper.updateNum(params);
                    System.out.println("[用户：" + userId + "，线程：" + Thread.currentThread().getName() + "] ************* 下单成功，库存扣减：" + num);
                    return new ResponseEntity<>("用户[" + userId + "]下单成功", HttpStatus.OK);
                }
                else{
                    System.out.println("[用户：" + userId + "，线程：" + Thread.currentThread().getName() + "] 拿到锁了，但库存不足，无法下单，很遗憾");
                    return new ResponseEntity<>("库存不足，用户[" + userId + "]下单失败", HttpStatus.NOT_FOUND);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            return new ResponseEntity<>("下单失败，发生异常：" + e.getLocalizedMessage(), HttpStatus.NOT_FOUND);
        } finally{
            try {
                //释放锁
                lock.release();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}